﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using TerrainGenerator.Geometry;

namespace TerrainGenerator
{
    class Terrain
    {
        float[,] heightmap;

        public int Width { get; set; }
        public int Height { get; set; }

        int[] indices;

        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;

        BasicEffect basicEffect;

        VertexPositionColor[] normals; // debug stuff, can delete after debugging
        VertexBuffer normalVertexBuffer; // ditto

        public Terrain(float[,] heightmap)
        {
            this.heightmap = heightmap;
            Width = heightmap.GetLength(0);
            Height = heightmap.GetLength(1);
        }

        public float GetValue(int x, int y)
        {
            return heightmap[x, y];
        }

        public float GetValue(int idx)
        {
            int x = idx % Width;
            int y = idx / Width;
            return GetValue(x, y);
        }

        public void GenerateBuffers(GraphicsDevice graphicsDevice)
        {
            basicEffect = new BasicEffect(graphicsDevice, null);

            VertexPositionNormalColored[] vertices = new VertexPositionNormalColored[Width * Height];

            //VertexPositionColor[] vertices = new VertexPositionColor[Width * Height];

            // calculate vertex positions
            for (int y = 0; y < Height; ++y)
            {
                for (int x = 0; x < Width; ++x)
                {
                    //vertices[y * Width + x] = new VertexPositionColor(new Vector3(x, heightmap[x, y], y), Color.GreenYellow);
                    vertices[y * Width + x] = new VertexPositionNormalColored(new Vector3(x, heightmap[x, y], y), Vector3.Zero, Color.Green);
                }
            }

            // calculate indices
            indices = new int[6 * (Width - 1) * (Height - 1)];

            int idx = 0;
            for (int y = 0; y < Height - 1; ++y)
            {
                for (int x = 0; x < Width - 1; ++x)
                {
                    int n = y * Width + x;
                    indices[idx++] = n;
                    indices[idx++] = n + 1;
                    indices[idx++] = n + Width;
                    indices[idx++] = n + 1;
                    indices[idx++] = n + 1 + Width;
                    indices[idx++] = n + Width;
                }
            }

            // calculate vertex normals
            for (int i = 0; i < indices.Length; i += 3)
            {
                Vector3 vec1 = vertices[indices[i + 1]].Position - vertices[indices[i]].Position;
                Vector3 vec2 = vertices[indices[i]].Position - vertices[indices[i + 2]].Position;
                Vector3 normal = Vector3.Cross(vec1, vec2);
                normal.Normalize();
                vertices[indices[i]].Normal += normal;
                vertices[indices[i + 1]].Normal += normal;
                vertices[indices[i + 2]].Normal += normal;
            }

            foreach (VertexPositionNormalColored vertex in vertices)
            {
                vertex.Normal.Normalize();
            }

            // set vertex and index buffers
            vertexBuffer = new VertexBuffer(graphicsDevice, typeof(VertexPositionNormalColored), vertices.Length, BufferUsage.WriteOnly);
            vertexBuffer.SetData(vertices);

            indexBuffer = new IndexBuffer(graphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);
            indexBuffer.SetData(indices);

            
            // debug code, store normals for rendering
            normals = new VertexPositionColor[vertices.Length*2];
            for (int i = 0; i < normals.Length; i += 2)
            {
                normals[i] = new VertexPositionColor(vertices[i/2].Position, vertices[i/2].Color);
                normals[i + 1] = new VertexPositionColor(vertices[i/2].Position + (vertices[i/2].Normal * 1), vertices[i/2].Color);
            }
            normalVertexBuffer = new VertexBuffer(graphicsDevice, typeof(VertexPositionColor), normals.Length, BufferUsage.WriteOnly);
            normalVertexBuffer.SetData(normals);
        }

        public void Print()
        {
            for (int x = 0; x < Width; ++x)
            {
                for (int y = 0; y < Height; ++y)
                {
                    System.Console.Write((int)(1000*heightmap[x, y]));
                    System.Console.Write('\t');
                }
                System.Console.Write('\n');
            }

            System.Console.WriteLine("Indices:");
            foreach (int idx in indices)
            {
                System.Console.Write(idx);
                System.Console.Write(' ');
            }
        }

        public void Draw(GraphicsDevice graphicsDevice, Matrix world, Matrix view, Matrix projection)
        {
            basicEffect.Begin();
            basicEffect.World = world;
            basicEffect.View = view;
            basicEffect.Projection = projection;
            basicEffect.VertexColorEnabled = true;
            basicEffect.EnableDefaultLighting();

            graphicsDevice.VertexDeclaration = new VertexDeclaration(graphicsDevice, VertexPositionNormalColored.VertexElements);

            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                graphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionNormalColored.SizeInBytes);
                graphicsDevice.Indices = indexBuffer;
                graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, Width * Height, 0, 2 * (Width - 1) * (Height - 1));
                pass.End();
            }

            basicEffect.End();
        }

        public void DrawNormals(GraphicsDevice graphicsDevice, Matrix world, Matrix view, Matrix projection)
        {
            basicEffect.Begin();
            basicEffect.World = world;
            basicEffect.View = view;
            basicEffect.Projection = projection;
            basicEffect.VertexColorEnabled = true;

            graphicsDevice.VertexDeclaration = new VertexDeclaration(graphicsDevice, VertexPositionColor.VertexElements);

            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                graphicsDevice.Vertices[0].SetSource(normalVertexBuffer, 0, VertexPositionColor.SizeInBytes);
                graphicsDevice.DrawPrimitives(PrimitiveType.LineList, 0, normals.Length / 2);
                pass.End();
            }

            basicEffect.End();
        }
    }
}
